介绍
观察者(Observer)模式:定义对象间的一种一个对多的依赖关系,当一个对象的状态发送改变时,所以依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布—订阅模式、模型—视图模式,它是对象行为型模式。
观察者模式是一个使用率非常高的模式,它最常用的地方是 GUI 系统、订阅——发布系统。因为这个模式的一个重要作用就是解耦,将被观察者和观察者解耦,使得它们之间的依赖性更小,甚至做到毫无依赖。
优点
- 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
- 目标与观察者之间建立了一套触发机制。
缺点
- 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
- 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。
使用场景
- 关联行为场景。需要注意的是,关联行为是可拆分的,而不是“组合”关系。
- 事件多级触发场景。
- 跨系统的信息交换场景,如消息队列、事件总线的处理机制。
使用例子
- 常见的发布—订阅模式。
- ListView 的 Adapter 的 notifyDataSetChanged 更新方法。
- BroadcastReceiver。
- 开源库 EventBus。
- RxJava。
结构与实现
观察者模式包含以下主要角色。
- 抽象主题(Subject):也就是被观察(Observable)的角色。它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
- 具体主题(ConcreteSubject):也就是具体被观察者(ConcreteObservable)。它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
- 抽象观察者(Observer):它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
- 具体观察者(ConcreteObserver):实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。
其结构图如下图所示。
代码如下:
我们也可以利用 JDK 中 Observable 类和 Observer 接口实现。观察者实现 Observer 接口,被观察者继承 Observable 类。被观察者通过 Observable 类的 addObserver 方法添加观察者。其代码形式如下:
被观察者通过 setChanged() 方法标示改变,通过 notifyObservers 方法通知所有观察者。notifyObservers 方法会遍历所有的观察者 Observer,并调用它们的 update 方法。notifyObservers 方法中的参数就是最后传到观察者 update 方法的参数 Object arg。
示例
利用观察者模式设计一个程序,分析“人民币汇率”的升值或贬值对进口公司的进口产品成本或出口公司的出口产品收入以及公司的利润率的影响。
分析:当“人民币汇率”升值时,进口公司的进口产品成本降低且利润率提升,出口公司的出口产品收入降低且利润率降低;当“人民币汇率”贬值时,进口公司的进口产品成本提升且利润率降低,出口公司的出口产品收入提升且利润率提升。
这里的汇率(Rate)类是抽象目标类,它包含了保存观察者(Company)的 List 和增加/删除观察者的方法,以及有关汇率改变的抽象方法 change(int number);而人民币汇率(RMBRate)类是具体目标, 它实现了父类的 change(int number) 方法,即当人民币汇率发生改变时通过相关公司;公司(Company)类是抽象观察者,它定义了一个有关汇率反应的抽象方法 response(int number);进口公司(ImportCompany)类和出口公司(ExportCompany)类是具体观察者类,它们实现了父类的 response(int number) 方法,即当它们接收到汇率发生改变的通知时作为相应的反应。下图所示是其结构图。
程序代码如下:
程序运行结果如下:
ANDROID 源码中的实现
ListView 是 ANDROID 中最重要的控件之一,而 ListView 最重要的一个功能就是 Adapter。当我们往 ListView 添加数据后,都会调用 Adapter 的 notifyDataSetChanged() 方法,通知界面刷新。这是一个典型的观察者模式案例。我们追踪 notifyDataSetChanged() 这个方法,它定义在 BaseAdapter 中,具体代码如下。
追踪 mDataSetObservable.notifyChanged() 函数。
而这些观察者是 ListView 通过 setAdapter 方法产生的。
观察者 AdapterDataSetObserver 定义在 ListView 的父类 AbsListView 中,具体代码如下。
通过 super.onChanged() 继续跟踪。
到这里我们就知道了,当 ListView 的数据发生变化时,调用 Adapter 的 notifyDataSetChanged 函数,这个函数又会调用 DataSetObservable 的 notifyChanged 函数,这个函数会调用所有观察者(AdapterDataSetObserver)的 onChanged 方法,在 onChanged 函数中又会调用 ListView 重新布局的函数使得 ListView 刷新界面。这就是一个观察者模式!
实战
利用观察者模式设计一个学校铃声的事件处理程序。
分析:在本实例中,学校的“铃”是事件源和目标,“老师”和“学生”是事件监听器和具体观察者,“铃声”是事件类。学生和老师来到学校的教学区,都会注意学校的铃,这叫事件绑定;当上课时间或下课时间到,会触发铃发声,这时会生成“铃声”事件;学生和老师听到铃声会开始上课或下课,这叫事件处理。这个实例非常适合用观察者模式实现,下图给出了学校铃声的事件模型。
现在用“观察者模式”来实现该事件处理模型。首先,定义一个铃声事件(RingEvent)类,它记录了铃声的类型(上课铃声/下课铃声);再定义一个学校的铃(BellEventSource)类,它是事件源,是观察者目标类,该类里面包含了监听器容器 listener,可以绑定监听者(学生或老师),并且有产生铃声事件和通知所有监听者的方法;然后,定义一声事件监听者(BellEventListener)类,它是抽象观察者,它包含了铃声事件处理方法 heardBell(RingEvent e);最后,定义老师类(TeachEventListener)和学生类(StuEventListener),它们是事件监听器,是具体观察者,听到铃声会去上课或下课。下图给出了学校铃声事件处理程序的结构。
程序代码如下:
程序运行结果如下: